
package JET.physics;

import JET.physics.engine.JETContext;
import JET.Centers.ControlCenter;
import JET.physics.bodies.SpringBody;
import JET.Constants;
import JET.geom.Rect2d;
import JET.geom.Vec2d;
import JET.physics.interfaces.SceneTreeNode;
import java.util.ArrayList;

/**
 *
 * @author 057Ry
 */
public final class RSpring implements Constants, SceneTreeNode {
    
    public enum Type {
        Engine,
        SpringBody,
        SceneItem,
        ShapeMatch
    }
    public static int id = 0;
    private int sid;

    private String name;

    private double ks = 1000.;
    private double kd = 0.5;
    private double startAngle;
    private Mass m1,m2,m3;
    private Vec2d a,b;
    private boolean alive = true;
    private Type type = Type.Engine;

    public RSpring(Mass m1, Mass m2, Mass m3, Type t) {
        
        sid = id++;
        name = "RSpring_"+sid;
        
        a = new Vec2d();
        b = new Vec2d();

        this.m1 = m1;
        this.m2 = m2;
        this.m3 = m3;

        if (m1==null || m2==null || m3==null)   this.startAngle = Math.PI;
        else                                    this.startAngle = getAngle();
        if (startAngle<0) {
            this.m1 = m3;
            this.m3 = m1;
            startAngle *= -1;
        }

        type = t;
    }

    public RSpring(Mass m1, Mass m2, Mass m3, double startAngle, Type t) {
        sid = id++;
        name = "RSpring_"+sid;
        
        a = new Vec2d();
        b = new Vec2d();

        this.m1 = m1;
        this.m2 = m2;
        this.m3 = m3;
        this.startAngle = startAngle;
        type = t;
    }

    public Type getType() {
        return type;
    }
    public void setType(Type t) {
        type = t;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }

    public void setAlive(boolean alive) {
        this.alive = alive;
    }

    public boolean isAlive() {
        return alive;
    }


    /**
     * @return first rotary spring mass.
     */
    public Mass getM1() {
        return m1;
    }

    /**
     * @return center (second) rotary spring mass.
     */
    public Mass getM2() {
        return m2;
    }

    /**
     * @return third roatary spring mass.
     */
    public Mass getM3() {
        return m3;
    }

    public void setM1(Mass m) {m1 = m;}
    public void setM2(Mass m) {m2 = m;}
    public void setM3(Mass m) {m3 = m;}

    public double getKs() {
        return ks;
    }

    public double getKd() {
        return kd;
    }

    /**
     * Sets damping factor of rotary spring.
     * @param kd damping factor
     */
    public void setKd(double kd) {
        this.kd = kd;
    }

    /**
     * Sets the spring constant ( from Hook's law : F = - ks * x ).
     * @param ks spring constant
     */
    public void setKs(double ks) {
        this.ks = ks;
    }

    /**
     * Sets angle of equilibrium of rotary spring.
     * @param startAngle angle of equilibrium
     */
    public void setStartAngle(double startAngle) {
        this.startAngle = startAngle;
    }

    public void recalculateStartAngle() {
        this.startAngle = getAngle();
        if (startAngle<0) {
            this.m1 = m3;
            this.m3 = m1;
            startAngle *= -1;
        }
    }
    /**
     * @return angle of equilibrium
     */
    public double getStartAngle() {
        return startAngle;
    }

    /**
     * @return vector A of rotary spring (from center mass to first mass)
     */
    public Vec2d getA() {
        this.a.become(m1.getPosition().getSubbed(m2.getPosition()));
        return this.a;
    }

    /**
     * @return vector B of rotary spring (from center mass to third mass)
     */
    public Vec2d getB() {
        this.b.become(m3.getPosition().getSubbed(m2.getPosition()));
        return this.b;
    }

    /**
     * Calculates actual angle between A nd B vectors, of rotary spring.
     * @return actual angle of rotary spring in radians
     */
    public double getAngle() {
        return getA().getAngleX() - getB().getAngleX();
    }

    /**
     * Calculates and assignes forces to first and third masses.
     */
    public void doYourJob()
    {
        if (alive) {
            double dAngle=0.;
            double angle=getAngle();

            Vec2d aPN = a.getPerp().getNormalized();
            Vec2d bPN = b.getPerp().getNormalized();

            dAngle = angle - startAngle;
            if (angle<0) dAngle += 2*Math.PI;

            double F1 =  -ks*dAngle / getA().getMagnitude();
            double F3 =  ks*dAngle / getB().getMagnitude();

            double v1 = m1.getVelocity().dot(aPN);
            double v3 = m3.getVelocity().dot(bPN);

            double FD = -kd*(v1-v3);

            Vec2d f1 = new Vec2d(aPN.getScaled(F1+FD));
            Vec2d f3 = new Vec2d(bPN.getScaled(F3-FD));

            Vec2d f2 = f1.getScaled(-1);
            f2.add(f3.getScaled(-1));

            m1.getForce().add( f1 );
            m2.getForce().add( f2 );
            m3.getForce().add( f3 );
        }
    }

    public Rect2d getCatchArea(int index, double catchAreaScale) {
        double x=0,y=0;
        switch(index) {
            case 1 : 
                x = this.getA().getMidVec(.5).getAdded(this.getM2().getPosition()).getX();
                y = this.getA().getMidVec(.5).getAdded(this.getM2().getPosition()).getY();
                break;
            case 2 :
                x = this.m2.getPosition().getX();
                y = this.m2.getPosition().getY();
            case 3 :
                x = this.getB().getMidVec(.5).getAdded(this.getM2().getPosition()).getX();
                y = this.getB().getMidVec(.5).getAdded(this.getM2().getPosition()).getY();
                break;
        }

        
        return new Rect2d( x - SPRING2D_CATCH_AREA_SIZE/catchAreaScale, y - SPRING2D_CATCH_AREA_SIZE/catchAreaScale,
                           x + SPRING2D_CATCH_AREA_SIZE/catchAreaScale, y + SPRING2D_CATCH_AREA_SIZE/catchAreaScale );
    }

    @Override
    public String toString() {
        return name;
    }

    ////////////////////////////////////////////////////////////////////////////
    //                S C E N E   T R E E   I N T E R F A C E                 //
    ////////////////////////////////////////////////////////////////////////////

    public NameTreeNode nameTreeNode = new NameTreeNode();
    public KsTreeNode ksTreeNode = new KsTreeNode();
    public KdTreeNode kdTreeNode = new KdTreeNode();
    public StartAngleTreeNode startLengthTreeNode = new StartAngleTreeNode();
    public AliveTreeNode aliveTreeNode = new AliveTreeNode();

    public SceneTreeNode getChild(int index) {
        switch (index) {
            case 0 : return nameTreeNode;
            case 1 : return ksTreeNode;
            case 2 : return kdTreeNode;
            case 3 : return startLengthTreeNode;
            case 4 : return aliveTreeNode;
            case 5 : return m1;
            case 6 : return m2;
        }

        return null;
    }

    public int getChildCount() {
        return 7;
    }

    public boolean isLeaf() {
        return false;
    }

    public int getIndexOfChild(Object child) {
        if      (child instanceof NameTreeNode) return 0;
        if      (child instanceof KsTreeNode) return 1;
        if      (child instanceof KdTreeNode) return 2;
        if      (child instanceof StartAngleTreeNode) return 3;
        if      (child instanceof AliveTreeNode) return 4;
        else if  (child instanceof Mass && child.equals(m1)) return 5;
        else if  (child instanceof Mass && child.equals(m2)) return 6;
        else if  (child instanceof Mass && child.equals(m3)) return 7;
        else
            return -1;
    }

    public void resetNodeValue(Object o) {}

    // Nodes Internal Classes

    public class NameTreeNode implements SceneTreeNode {
        public SceneTreeNode getChild(int index) {return null;}
        public int getChildCount() {return 0;}
        public boolean isLeaf() {return true;}
        public int getIndexOfChild(Object child) {return -1;}

        public void resetNodeValue(Object o) {
            name = o.toString();
        }

        public String toString() {
            return "name: '"+name+"'";
        }
    }

    public class KsTreeNode implements SceneTreeNode {
        public SceneTreeNode getChild(int index) {return null;}
        public int getChildCount() {return 0;}
        public boolean isLeaf() {return true;}
        public int getIndexOfChild(Object child) {return -1;}

        public void resetNodeValue(Object o) {
            try {
                ks = Double.parseDouble(o.toString());
                ControlCenter.getInstance().GUIC.repaintAllSceneTabs();
            } catch(NumberFormatException nfe) {}
        }

        public String toString() {
            return "ks: '"+ks+"'";
        }
    }

    public class KdTreeNode implements SceneTreeNode {
        public SceneTreeNode getChild(int index) {return null;}
        public int getChildCount() {return 0;}
        public boolean isLeaf() {return true;}
        public int getIndexOfChild(Object child) {return -1;}

        public void resetNodeValue(Object o) {
            try {
                kd = Double.parseDouble(o.toString());
                ControlCenter.getInstance().GUIC.repaintAllSceneTabs();
            } catch(NumberFormatException nfe) {}
        }

        public String toString() {
            return "kd: '"+kd+"'";
        }
    }

    public class StartAngleTreeNode implements SceneTreeNode {
        public SceneTreeNode getChild(int index) {return null;}
        public int getChildCount() {return 0;}
        public boolean isLeaf() {return true;}
        public int getIndexOfChild(Object child) {return -1;}

        public void resetNodeValue(Object o) {
            try {
                startAngle = Double.parseDouble(o.toString());
                ControlCenter.getInstance().GUIC.repaintAllSceneTabs();
            } catch(NumberFormatException nfe) {}
        }

        public String toString() {
            return "start angle: '"+startAngle+"'";
        }
    }

//    public class TearableTreeNode implements SceneTreeNode {
//        public SceneTreeNode getChild(int index) {return null;}
//        public int getChildCount() {return 0;}
//        public boolean isLeaf() {return true;}
//        public int getIndexOfChild(Object child) {return -1;}
//
//        public void resetNodeValue(Object o) {
//            tearable = Boolean.parseBoolean(o.toString());
//            ControlCenter.getInstance().GUIC.repaintAllSceneTabs();
//        }
//
//        public String toString() {
//            return "tearable: "+tearable;
//        }
//    }

    public class AliveTreeNode implements SceneTreeNode {
        public SceneTreeNode getChild(int index) {return null;}
        public int getChildCount() {return 0;}
        public boolean isLeaf() {return true;}
        public int getIndexOfChild(Object child) {return -1;}

        public void resetNodeValue(Object o) {
            alive = Boolean.parseBoolean(o.toString());
            ControlCenter.getInstance().GUIC.repaintAllSceneTabs();
        }

        public String toString() {
            return "alive: "+alive;
        }
    }
}
